package hzncc.wcs.util;

import com.diboot.core.util.S;
import com.diboot.file.util.FileHelper;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.rmi.RemoteException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class WordUtils {
    /**
     * 对word进行参数替换并生成新的文件
     *
     * @param inputPath word模板路径和名称
     * @param outPath   word模板路径和名称
     * @param params    待填充的数据 params.put("key",value) 文档中对应为 ${key}
     */
    public static void readwriteWord(String inputPath, String outPath, Map<String, Object> params) throws IOException, InvalidFormatException {

        InputStream is = new FileInputStream(inputPath);
        XWPFDocument document = new XWPFDocument(OPCPackage.open(is));
        replaceParams(document, params);
        OutputStream os = new FileOutputStream(outPath);
        document.write(os);
        close(os);
        close(is);
    }

    /**
     * 替换word中的参数与动态新增表格行数
     *
     * @param in         模板输入流
     * @param os         修改后的输出流
     * @param params     待填充的数据 params.put("key",value) 文档中对应为 ${key}
     * @param tableIndex 表格下标，从0开始，如Word中3个表格，仅对1、3个表格进行修改，参数为 int[]{0,2}
     * @param tables     新增表格的数据，数据格式为 [表[行[单元格]]]
     * @throws InvalidFormatException
     * @throws IOException
     */
    public static void readwriteWord(InputStream in, OutputStream os, Map<String, Object> params, int[] tableIndex, List<List<String[]>> tables) throws IOException, InvalidFormatException {
        XWPFDocument document = new XWPFDocument(OPCPackage.open(in));
        replaceParams(document, params);
        if (tableIndex.length != tables.size()) throw new RemoteException("表格下标数量与表格参数数量不一致！");
        for (int i = 0; i < tableIndex.length; i++) {
            changeTable(document, params, i, tables.get(i), tableIndex[i]);
        }
        document.write(os);
    }

    /**
     * 实现对word读取和修改操作
     *
     * @param in     模板输入流
     * @param out    输出流
     * @param params 待填充的数据，从数据库读取
     * @throws IOException
     * @throws InvalidFormatException
     */
    public static void readwriteWord(InputStream in, OutputStream out, Map<String, Object> params) {
        try {
            XWPFDocument document;
            document = new XWPFDocument(OPCPackage.open(in));
            replaceParams(document, params);
            document.write(out);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 替换段落里面的变量
     *
     * @param doc    要替换的文档
     * @param params 参数
     */
    private static void replaceParams(XWPFDocument doc, Map<String, Object> params) {
        Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
        XWPFParagraph paragraph;
        while (iterator.hasNext()) {
            paragraph = iterator.next();
            replaceParam(paragraph, params);
        }
    }

    /**
     * @param doc        docx解析对象
     * @param params     需要替换的信息集合
     * @param tableIndex 第几个表格
     * @param tableList  需要插入的表格信息集合
     * @param index      从index行之后插入
     */
    public static void changeTable(XWPFDocument doc, Map<String, Object> params, int tableIndex, List<String[]> tableList, int index) {
        //获取表格对象集合
        List<XWPFTable> tables = doc.getTables();
        //获取第一个表格   根据实际模板情况 决定去第几个word中的表格
        XWPFTable table = tables.get(tableIndex);
        //替换表格中的参数
        replaceTableParams(doc, params);
        //在表格中插入数据
        insertTable(table, tableList, index);
    }

    /**
     * 替换表格里面的变量
     *
     * @param doc    要替换的文档
     * @param params 参数
     */
    private static void replaceTableParams(XWPFDocument doc, Map<String, Object> params) {
        Iterator<XWPFTable> iterator = doc.getTablesIterator();
        XWPFTable table;
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        while (iterator.hasNext()) {
            table = iterator.next();
            //判断表格是需要替换还是需要插入，判断逻辑有$为替换，表格无$为插入
            if (matcher(table.getText()).find()) {
                rows = table.getRows();
                for (XWPFTableRow row : rows) {
                    cells = row.getTableCells();
                    for (XWPFTableCell cell : cells) {
                        paras = cell.getParagraphs();
                        for (XWPFParagraph para : paras) {
                            replaceParam(para, params);
                        }
                    }
                }
            }

        }
    }

    /**
     * 为表格插入行数，此处不处理表头，所以从第二行开始
     *
     * @param table     需要插入数据的表格
     * @param tableList 插入数据集合
     * @param index     数据行
     */
    private static void insertTable(XWPFTable table, List<String[]> tableList, int index) {
        if (tableList == null || tableList.size() == 0) {
            return;
        }
        //创建与数据一致的行数
        for (int i = 0; i < tableList.size(); i++) {
            table.insertNewTableRow(index + i);
        }
        for (int i = 0; i < tableList.size(); i++) {
            int tableRow = index + i;
            XWPFTableRow newRow = table.getRow(tableRow);
            List<Integer> mergeList = new ArrayList<>();
            List<XWPFTableCell> cells = newRow.getTableCells();
            if (cells.isEmpty()) {
                String[] data = tableList.get(i);
                for (int idx = 0; idx < data.length; idx++) {
                    String datum = data[idx];
                    XWPFTableCell cell = newRow.createCell();
                    // 如果数据为‘#’表示为合并单元格，不显示数据
                    if (S.equals(datum, "#")) {
                        cell.setText("");
                        // 记录需要合并单元格序号
                        mergeList.add(idx);
                    } else {
                        if (datum == null || S.equals(datum, "null")){
                            datum = "";
                        }
                        cell.setText(datum);
                    }
                    setCellBorders(cell, "single", "single", "single", "single");
                    setCellAlignment(cell, "center", "center");
                }
            } else {
                for (int j = 0; j < cells.size(); j++) {
                    XWPFTableCell cell = cells.get(j);
                    cell.setText(tableList.get(i)[j]);
                    setCellBorders(cell, "single", "single", "single", "single");
                }
            }
            for (Integer idx : mergeList) {
                // 合并单元格
                mergeCells(newRow, idx - 1, idx);
            }
        }
    }


    /**
     * 设置单元格对齐方式
     * @param cell 单元格对象
     * @param horizontal 水平对齐方式
     * @param vertical 垂直对齐方式
     */
    private static void setCellAlignment(XWPFTableCell cell, String horizontal, String vertical) {
        CTTcPr tcPr = cell.getCTTc().getTcPr();
        if (tcPr == null) {
            tcPr = cell.getCTTc().addNewTcPr();
        }

        // 设置水平对齐方式
        cell.getCTTc().getPList().get(0).addNewPPr().addNewJc().setVal(STJc.Enum.forString(horizontal));
        // 设置垂直对齐方式
        CTVerticalJc vAlign = tcPr.addNewVAlign();
        vAlign.setVal(STVerticalJc.Enum.forString(vertical));
    }

    /**
     * 合并单元格
     *
     * @param row      表格行
     * @param startCol 开始列
     * @param endCol   结束列
     */
    private static void mergeCells(XWPFTableRow row, int startCol, int endCol) {
        for (int i = startCol; i <= endCol; i++) {
            XWPFTableCell cell = row.getCell(i);
            CTTcPr tcPr = cell.getCTTc().getTcPr();
            if (tcPr == null) {
                tcPr = cell.getCTTc().addNewTcPr();
            }

            if (i == startCol) {
                CTHMerge hMerge = tcPr.addNewHMerge();
                hMerge.setVal(STMerge.RESTART);
            } else {
                CTHMerge hMerge = tcPr.addNewHMerge();
                hMerge.setVal(STMerge.CONTINUE);
            }
        }
    }

    /**
     * 设置单元格边框
     * @param cell 单元格对象
     * @param top 上边框
     * @param bottom 底部边框
     * @param left 左边框
     * @param right 右边框
     */
    private static void setCellBorders(XWPFTableCell cell, String top, String bottom, String left, String right) {
        CTTcPr tcPr = cell.getCTTc().getTcPr();
        if (tcPr == null) {
            tcPr = cell.getCTTc().addNewTcPr();
        }

        CTTcBorders borders = tcPr.isSetTcBorders() ? tcPr.getTcBorders() : tcPr.addNewTcBorders();

        CTBorder topBorder = borders.isSetTop() ? borders.getTop() : borders.addNewTop();
        topBorder.setVal(STBorder.Enum.forString(top));
        topBorder.setSz(BigInteger.valueOf(4)); // 边框粗细

        CTBorder bottomBorder = borders.isSetBottom() ? borders.getBottom() : borders.addNewBottom();
        bottomBorder.setVal(STBorder.Enum.forString(bottom));
        bottomBorder.setSz(BigInteger.valueOf(4)); // 边框粗细

        CTBorder leftBorder = borders.isSetLeft() ? borders.getLeft() : borders.addNewLeft();
        leftBorder.setVal(STBorder.Enum.forString(left));
        leftBorder.setSz(BigInteger.valueOf(4)); // 边框粗细

        CTBorder rightBorder = borders.isSetRight() ? borders.getRight() : borders.addNewRight();
        rightBorder.setVal(STBorder.Enum.forString(right));
        rightBorder.setSz(BigInteger.valueOf(4)); // 边框粗细
    }

    /**
     * 替换段落里面的变量
     *
     * @param paragraph 要替换的段落
     * @param params    参数
     */
    private static void replaceParam(XWPFParagraph paragraph, Map<String, Object> params) {
        List<XWPFRun> runs;
        Matcher matcher;
        String runText = "";

        if (matcher(paragraph.getParagraphText()).find()) {
            runs = paragraph.getRuns();
            int j = runs.size();
            for (int i = 0; i < j; i++) {
                runText += runs.get(0).toString();
                //保留最后一个段落，在这段落中替换值，保留段落样式
                if (!((j - 1) == i)) {
                    paragraph.removeRun(0);
                }
            }
            matcher = matcher(runText);
            if (matcher.find()) {
                while ((matcher = matcher(runText)).find()) {
                    runText = matcher.replaceFirst(Optional.ofNullable(params.get(matcher.group(1))).orElse("").toString());
                }
                runs.get(0).setText(runText, 0);
            }
        }

    }

    /**
     * 正则匹配字符串
     *
     * @param str
     * @return
     */
    private static Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    /**
     * 关闭输入流
     *
     * @param is
     */
    private static void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭输出流
     *
     * @param os
     */
    private static void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void addFolderToZip(String parentPath, String sourceFolder, ZipOutputStream zos) throws IOException {
        File folder = new File(sourceFolder);
        for (File file : folder.listFiles()) {
            if (file.isDirectory()) {
                addFolderToZip(parentPath + file.getName() + "/", file.getAbsolutePath(), zos);
            } else {
                addFileToZip(parentPath, file, zos);
            }
        }
    }

    public static void addFileToZip(String path, File file, ZipOutputStream zos) throws IOException {
        FileInputStream fis = new FileInputStream(file);
        ZipEntry zipEntry = new ZipEntry(path + file.getName());
        zos.putNextEntry(zipEntry);

        byte[] bytes = new byte[1024];
        int length;
        while ((length = fis.read(bytes)) >= 0) {
            zos.write(bytes, 0, length);
        }

        zos.closeEntry();
        fis.close();
    }

    public static void addFileToZip(ZipOutputStream zos, String filePath) throws IOException {
        File file = new File(filePath);
        FileInputStream fis = new FileInputStream(file);
        ZipEntry zipEntry = new ZipEntry(file.getName());
        zos.putNextEntry(zipEntry);

        byte[] bytes = new byte[1024];
        int length;
        while ((length = fis.read(bytes)) >= 0) {
            zos.write(bytes, 0, length);
        }

        zos.closeEntry();
        fis.close();
    }

    public static String getResourceFilePath(String resourcePath) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream(resourcePath);
        if (resourceAsStream == null) {
            throw new RuntimeException("未找到模板文件");
        }
        String systemTempDir = FileHelper.getSystemTempDir().replaceAll("\\\\", "/");
        if (!systemTempDir.endsWith("/")) {
            systemTempDir += "/";
        }
        // 创建临时文件
        File tempFile = null;
        try {
            tempFile = new File(systemTempDir, System.currentTimeMillis()+resourcePath.substring(resourcePath.lastIndexOf("/") + 1));
            Files.copy(resourceAsStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new RuntimeException("创建临时文件失败", e);
        }
        return tempFile.getPath();
    }

    public static String getTemplateFilePath() {
        String systemTempDir = FileHelper.getSystemTempDir();
        if (!systemTempDir.endsWith("/")) {
            systemTempDir += "/";
        }
        systemTempDir += "crm-temp/";
        File tempDir = new File(systemTempDir);
        if (!tempDir.exists()) {
            tempDir.mkdir();
        }
        return systemTempDir;
    }
}
 